import pandas as pd
import numpy as np


freq_map = {'日': 'D', '周': 'W', '月': 'M', '季': 'Q', '年': 'Y'}


class TjdSingleData:
    """
    用于腾景可以进行计算的数据类，里面只包含一条数据，允许不同频率
    """

    def __init__(self, raw_data: pd.Series, name=None, freq=None, data_type=None, source=None,
                 history_name=None):
        if len(raw_data.dropna()) == 0:
            print(raw_data)
        assert isinstance(raw_data, pd.Series)
        assert len(raw_data.dropna()) > 0

        self.chinese_freq_li = ['日', '周', '月', '季', '年']
        self.english_freq_li = ['D', 'W', 'M', 'Q', 'Y']
        self.freq_cn_to_en = dict(zip(self.chinese_freq_li, self.english_freq_li))
        self.yoy_mom_li = ['当月同比', '当月环比', '当季同比', '当季环比', '当年同比', '当旬环比', '当日环比']
        self.yoy_mom_li_pct_args = [12, 1, 4, 1, 1, 1, 1]
        raw_data.index = pd.to_datetime(raw_data.index)  # 变成时间格式数据
        raw_data.sort_index(inplace=True)

        if source:
            self.source = source
        else:
            self.source = 'Unsourced'

        self.cut = pd.to_datetime('2006-01-01')
        self.raw = raw_data.copy()  # 保留最原始的数据，以便后续debug使用

        self.data, self.name = self.ini_data(name)
        self.num = len(self.data)
        self.start_time = self.data.index[0]
        self.end_time = self.data.index[-1]

        if freq:  # 如果freq不为None，就用传入的freq，否则自动识别freq
            self.freq = freq
        else:
            self.freq = self.auto_detect_freq()

        self.nan_ratio = self.auto_detect_nan_ratio()  # 计算nan比例，从第一个不为nan的数开始

        if data_type:  # 按照程序逻辑，data_type的识别一定要晚于name的识别
            self.data_type = data_type
        elif '当月同比' in self.name:
            self.data_type = '当月同比'
        elif '当月值' in self.name:
            self.data_type = '当月均值'
        elif '累计值' in self.name:
            self.data_type = f'{self.freq}累计值'
        else:
            self.data_type = '原始值'

        # 为了记录数据的变换过程，比如['当日值', '当月均值', '当月同比']
        # 或者是['当周值', '当季均值', '当季环比']
        # 或者是['月累计值', '当月均值', '当月同比']
        if history_name:
            if self.name not in history_name:
                history_name.append(self.name)
            self.history_name = history_name
        else:
            self.history_name = [self.name]

        self.target_freq = None
        self.cum = None
        self.ma = None
        self.lag = None

    def __getstate__(self):
        state = self.__dict__.copy()
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)

    @property
    def scalar_data(self):
        return (self.data - self.data.mean()) / self.data.std()

    def save_path(self):  # 生成可以保存的字符串，防止在windows系统性下出现命名字符串冲突
        f = self.name
        invalid_chars = '<>:"/\\|?*'
        for char in invalid_chars:
            f = f.replace(char, 'r_r')
        return f

    @property
    def complete_data(self):
        """
        补全所有的月，季，年度数据的日期，有时候原始数据月度日期是不完备（某些月、季、年空缺）
        :return:
        """
        if self.freq == '旬':
            return self.data
        idxes = pd.date_range(self.data.index[0], self.data.index[-1], freq=self.freq_cn_to_en[self.freq])
        new_data = pd.Series(index=idxes, name=self.name, dtype='float')
        for idx in idxes:
            if idx in self.data.index:
                new_data[idx] = self.data[idx]
        if '累计值' in self.name:
            return new_data.fillna(method='bfill')
        else:
            return new_data.fillna(method='ffill')

    def cum_sum(self):
        data = pd.DataFrame(self.complete_data)
        data['year'] = data.index.year
        y_set = sorted(list(set(self.complete_data.index.year)))
        li = []
        for year, df in data.groupby(['year']):
            if isinstance(year, tuple):
                year = year[0]
            if year > y_set[0]:
                se = df.iloc[:, 0].cumsum()
                li.append(se)
        return pd.concat(li, axis=0)

    def get_w_m_q_y_func(self, text):  # 前值、均值、后值的选择
        resample = self.data.resample(self.freq_cn_to_en[text[1]])
        func_map = {'均': resample.mean, '前': resample.first, '后': resample.last}
        return func_map[text[2]]

    def get_w_m_q_y_yoy_mom_func(self, text):  # item 为当月同比, 当月环比, 当季同比, 当季环比 返回int值（用于pct_change），和频率
        return dict(zip(self.yoy_mom_li, self.yoy_mom_li_pct_args))[text], text[1]

    def every_ten_days_data(self):
        all_idx_date = []
        for idx in self.data.index:
            idx_str = str(idx)[:10]
            dt = DateTransformer(idx).dt
            if idx.day in [10, 20]:
                all_idx_date.append(idx)
            elif idx_str == dt['mld']:
                all_idx_date.append(idx)
            elif idx_str == str(self.data.index[-1])[:10]:
                idx_tran = pd.to_datetime(DateTransformer(idx).to_ten_day_end())
                all_idx_date.append(idx_tran)
        dic = {}
        for i in range(len(all_idx_date)):
            t_start = all_idx_date[i - 1]
            t_end = all_idx_date[i]
            dic[t_end] = self.data[t_start:t_end]
        return dic

    def daily_yoy(self):
        """
        计算当日同比，本日和去年同日比较，如果去年同日没数据，则不计算
        """
        assert len(self.data) > 365
        se = {}
        for t in self.data.index[365:]:
            t_last_year = pd.to_datetime(DateTransformer(t).last_year_today())
            if t_last_year in self.data.index:
                se[t] = self.data[t] / self.data[t_last_year] - 1
        kwargs = {'name': f"{self.name}_当日同比", 'source': self.source}
        return TjdSingleData(pd.Series(se), freq=self.freq, data_type='当日同比', **kwargs)

    def __getitem__(self, item):
        """
        :param item:当(周，月，季，年）均值，当（月，季，年）同比，滞后【i】期
        :return:
        """
        # print('item',item)
        self.data = self.data.astype(float)  # 各种变频 # 还没写好低频转高频怎么
        new_name = f"{self.name}" if item == '原始值' else f"{self.name}_{item}"
        kwargs = {'name': new_name, 'history_name': self.history_name, 'source': self.source}
        if item == '原始值':
            return self
        elif item == '当季前两月均值':
            dd = self.data 
            new_data_dic = {k:v for k,v in dd.items() if k.month not in [3, 6, 9, 12]}
            new_data = pd.Series(new_data_dic, name=self.data.name, dtype='float')
            resample = new_data.resample('Q')
            func_map = {'均': resample.mean, '前': resample.first, '后': resample.last}
            data_transformer = func_map[item[-2]]().dropna()
            return TjdSingleData(data_transformer, freq=item[1], data_type=item, **kwargs)
        elif item == '累计值':
            return TjdSingleData(self.cum_sum(), freq=self.freq, data_type=item, **kwargs)
        elif item in ['当月均值', '当月前值', '当月后值', '当周均值', '当周前值', '当周后值', 
                      '当季均值', '当季前值', '当季后值',
                      '当年均值', '当年前值', '当年后值']:
            data_transformer = self.get_w_m_q_y_func(item)().dropna()
            return TjdSingleData(data_transformer, freq=item[1], data_type=item, **kwargs)
        elif item in ['当旬均值', '当旬前值', '当旬后值']:
            dic = {}
            for t, sample in self.every_ten_days_data().items():
                sample = sample.dropna()
                if len(sample) == 0:
                    value = np.nan
                else:
                    if item == '当旬均值':
                        value = sample.mean()
                    elif item == '当旬前值':
                        value = sample[0]
                    else:
                        value = sample[0]
                dic[t] = value
            return TjdSingleData(pd.Series(dic), freq='旬', data_type=item, **kwargs)
        elif item in ['当旬同比']:
            assert self.freq == '旬'
            dic = {}
            for t in self.data.index[36:]:
                t_last_year = pd.to_datetime(DateTransformer(t).dt['ly_freq_10'])  # 上年同期数据
                if t_last_year in self.data:
                    dic[t] = self.data[t] / self.data[t_last_year] - 1
                else:
                    dic[t] = np.nan
            se = pd.Series(dic)
            return TjdSingleData(se, freq='旬', data_type=item, **kwargs)
        elif item in self.yoy_mom_li:  # 各种同比，环比
            pct_int, freq = self.get_w_m_q_y_yoy_mom_func(item)  # item 为当月同比, 当月环比, 当季同比, 当季环比
            se_yoy = self.complete_data.pct_change(pct_int)[self.data.index].dropna()
            return TjdSingleData(se_yoy, freq=freq, data_type=item, **kwargs)
        elif '滞后' in item:
            lag = int(item.replace('滞后', '').replace('期', ''))
            lag_data = self.complete_data.shift(lag)
            return TjdSingleData(lag_data, freq=self.freq, data_type=item, **kwargs)
        elif '移动平均' in item:
            ma_n = int(item.replace('移动平均', '').replace('期', ''))
            if ma_n > 0:
                data = FeatureTransformer(data=self.complete_data).ma(ma_n)
                return TjdSingleData(data, freq=self.freq, data_type=item, **kwargs)
        elif item in ['name', 'freq']:
            item_map = {'name': self.name, 'freq': self.freq}
            return item_map[item]
        else:
            pass
        # 如果都没有，返回原始值
        return self

    @property
    def p_freqs(self):
        if self.chinese_freq_li.index(self.freq) < self.chinese_freq_li.index(self.target_freq):
            return [
                # f'当{self.target_freq}前值',
                f'当{self.target_freq}均值',
                # f'当{self.target_freq}后值'
            ]
        else:
            return ['原始值']

    @property
    def p_yoys(self):
        # 如果数据本身已经是同比，则不需要计算同比
        if ('当月同比' in self.name) or ('当季同比' in self.name):
            return ['原始值']
        else:
            return [
                '原始值',
                f'当{self.target_freq}同比',
                # f'当{self.target_freq}环比'
            ]

    @property
    def ma_ns(self):
        li = ['原始值']
        if not self.cum:
            li += [f"移动平均{ma_n}期" for ma_n in list(range(self.ma[0], self.ma[1]))]
        return li

    @property
    def lag_ns(self):
        li = ['原始值']
        if not self.cum:
            li += [f"滞后{lag_n}期" for lag_n in list(range(self.lag[0], self.lag[1]))]
        return li

    def iter_all(self, target_freq='月', ma=(2, 4), lag=(1, 2), cum=False, yoy_first=True):
        """
        遍历所有低频转换为高频后的可能性1、[当freq{月季年}mode{前均后}值] 2、同比，环比 3、移动平均 4、趋势项、季节项
        如果同比在前，那么p2，在p3前面
        :return: 返回列表，每个元素用字符串 “_” 来拼接，用于目标数据的转换 , 后续用于入库，或者遍历计算特征工程
        """
        # 如果是日周频率，而目标频率是月和季度，需要降频，降频就需要 前值，均值，或者后值，如果本身就是目标频率就不需要降频
        if target_freq == '季' and cum:
            return ['当季均值_累计值_当季同比']

        self.target_freq = target_freq
        self.cum = cum
        self.ma = ma
        self.lag = lag

        p_cums = ['累计值'] if cum else ['原始值']
        trend_seasonal_li = ['原始值']  # '趋势项', '季节项',
        li = []
        itl = itertools.product(self.p_freqs, p_cums, self.p_yoys, self.ma_ns, trend_seasonal_li, self.lag_ns)
        for p_freq, p_cum, p_yoy, ma_n_str, trend_seasonal, lag_n in itl:
            li.append(self.concat_process_string(p_freq, p_cum, p_yoy, ma_n_str, trend_seasonal, lag_n, yoy_first))
        return li

    @staticmethod
    def concat_process_string(freq_string, cum_s, yoy_s, ma_n, trend_seasonal, lag_n, yoy_first=True):
        """
        组装字符串组成的处理
        :return:
        """
        if yoy_first:
            s = f"{freq_string}_{cum_s}_{yoy_s}_{trend_seasonal}_{ma_n}_{lag_n}"
        else:
            s = f"{freq_string}_{cum_s}_{trend_seasonal}_{ma_n}_{yoy_s}_{lag_n}"
        return s.replace('_原始值', '')

    def get_long_string_data(self, long_s):
        s_li = long_s.split('_')
        s_data = self
        for s in s_li:
            s_data = s_data[s]
        return s_data

    def __str__(self):
        return f"名称：{self.data.name},频率：{self.freq},数量：{self.num},类型：{self.data_type}"

    def auto_detect_freq(self) -> str:
        """
        通过数据间的间隔自动监测数据频率（按照经验参数）
        :return:str
        """
        if 'air_quality' in self.source:
            return '日'
        elif 'baidu' in self.source:
            return '日'
        elif 'g7_' in self.source:
            return '日'
        elif 'daily' in self.source:
            return '日'
        elif 'weekly' in self.source:
            return '周'
        elif 'monthly' in self.source:
            return '月'
        elif 'quarterly' in self.source:
            return '季'
        else:
            idx = sorted(self.data.index)
            if not isinstance(idx, pd.DatetimeIndex):
                idx = pd.DatetimeIndex(idx)
            mean_delta = np.mean([(idx[i] - idx[i - 1]).days for i in range(1, len(idx) - 1)])
            if mean_delta > 350:
                return '年'
            elif mean_delta > 85:
                return '季'
            elif mean_delta > 29:
                return '月'
            elif mean_delta > 9:
                return '旬'
            elif mean_delta > 5:
                return '周'
            else:
                return '未知'

    def auto_detect_nan_ratio(self):
        v = self.raw.dropna()
        if self.freq in self.chinese_freq_li:
            return max(0.001, float(1 - len(v) / len(pd.date_range(v.index[0], v.index[-1], freq=freq_map[self.freq]))))
        else:
            start = self.raw.dropna().index[0]
            raw_data = pd.Series(self.raw[max(start, self.cut):])  # 取第一个非nan数据作为开始
            return raw_data.isna().sum() / len(raw_data)  # 获取初始nan比例，前面的nan不算

    def ini_data(self, name):
        raw_data = self.raw.dropna()  # 之前drop后传给了raw_data_dropped,再drop一次
        if name:  # 如果name不为None，采用传入的名字，否则采用se本身的名字
            return pd.Series(raw_data, name=name), name
        else:
            return pd.Series(raw_data), raw_data.name

    def plot(self):
        return self.data.plot()


def to_monthly_mean(se):
    se.index = pd.to_datetime(se.index)
    df = pd.DataFrame(se)
    df['year'] = df.index.year
    df['month'] = df.index.month
    ot_df = pd.DataFrame(index=[f"{i}月" for i in range(1,13)], 
                         columns=sorted(df['year'].unique()), dtype=(float))
    for (year,month), df_year_month in df.groupby(['year', 'month']):
        ot_df.loc[f"{month}月", year] = df_year_month.mean()[se.name]
    return ot_df.round(1)


def to_quartely_mean(se):
    se.index = pd.to_datetime(se.index)
    df = pd.DataFrame(se)
    df['year'] = df.index.year
    df['quarter'] = df.index.quarter
    ot_df = pd.DataFrame(index=[f"{i}季" for i in range(1,5)], 
                         columns=sorted(df['year'].unique()), dtype=(float))
    for (year,month), df_year_month in df.groupby(['year', 'quarter']):
        ot_df.loc[f"{month}季", year] = df_year_month.mean()[se.name]
    return ot_df.round(1)


def to_daily(se):
    se.index = pd.to_datetime(se.index)
    df = pd.DataFrame(se)
    df['year'] = df.index.year
    collector = []
    for year, df_year in df.groupby(['year']):
        df_year.index = [str(s)[5:10] for s in df_year.index]
        se_for_concat = df_year[se.name]
        if se_for_concat.isna().sum() < len(se_for_concat):
            se_for_concat.name = year
            collector.append(se_for_concat)
    df = pd.concat(collector, axis=1)
    df = df.sort_index()
    return df


def gen_xy_same_freq(df_macro, y_name, x_name, x_process_string):
    df_hf = TjdSingleData(df_macro[x_name])
    if len(x_process_string) < 2:
        df_x = df_hf.data
    else:
        if '_' in x_process_string:
            df_x = df_hf.get_long_string_data(x_process_string).data
        else:
            df_x = df_hf[x_process_string].data
    if '同比' in x_process_string:
        df_x = df_x * 100
    df_y = df_macro[y_name].dropna()
    df_y.index = pd.to_datetime(df_y.index)
    df_x.index = pd.to_datetime(df_x.index)
    # 截短数据
    first_date = max(df_x.index[0], df_y.index[0])
    df_y = df_y[first_date:]
    df_x = df_x[first_date:]
    # 合并数据
    df_cot = pd.concat([df_y, df_x], axis=1)
    df_cot = df_cot.round(1)
    return df_cot


def cal_tztd_index(df):
    area_fill_li = []
    idx_li = [str(s)[:10] for s in df.index.to_list()]
    for i in range(len(idx_li)-1):
        idx_start = idx_li[i]
        idx_end = idx_li[i+1]
        y_diff = df.iloc[:,0][idx_end] - df.iloc[:,0][idx_start]
        x_diff = df.iloc[:,1][idx_end] - df.iloc[:,1][idx_start]
        if y_diff * x_diff > 0:
            area_fill_li.append((idx_start, idx_end))
    tztd_ratio = len(area_fill_li) / (len(idx_li) - 2)
    return area_fill_li, tztd_ratio